package no.sachmuld.methodcombination.examples.reflection.designbycontract.stack;

import no.sachmuld.methodcombination.api.MethodCombinationSupport;
import no.sachmuld.methodcombination.api.Condition;
import no.sachmuld.methodcombination.api.Context;
import no.sachmuld.methodcombination.api.MethodInvocation;
import no.sachmuld.methodcombination.api.reflection.DesignByContract;

import java.util.Deque;
import java.util.LinkedList;

public class Stack<T> extends MethodCombinationSupport<StackBehaviour<T>> {

    private Deque<T> list = new LinkedList<>();

    public Stack() {

        with(new StackBehaviour<T>() {

            @Override
            public Condition beforePush(T element, Context context) {
                context.put("oldSize", Stack.this.size());
                return new Condition("element not null", element != null);
            }

            @Override
            public void push(T element, MethodInvocation<Void> methodInvocation) {
                list.addFirst(element);
            }

            @Override
            public Condition afterPush(T element, Context context) {
                int oldSize = (Integer) context.get("oldSize");
                return new Condition("not empty", !Stack.this.empty()).
                        and("element on top", Stack.this.top() == element).
                        and("size increased by one", Stack.this.size() == oldSize + 1);
            }

            @Override
            public Condition beforeTop(Context context) {
                context.put("oldSize", Stack.this.size());
                return new Condition("not empty", !Stack.this.empty());
            }

            @Override
            public T top(MethodInvocation<T> methodInvocation) {
                return list.peekFirst();
            }

            @Override
            public Condition afterTop(T result, Context context) {
                int oldSize = (Integer) context.get("oldSize");
                return new Condition("size is unchanged", Stack.this.size() == oldSize);
            }

            @Override
            public Condition beforePop(Context context) {
                context.put("oldSize", Stack.this.size());
                if (!Stack.this.empty()) {
                    context.put("oldTop", Stack.this.top());
                }
                return new Condition("not empty", !Stack.this.empty());
            }

            @Override
            public T pop(MethodInvocation<T> methodInvocation) {
                return list.removeFirst();
            }

            @Override
            public Condition afterPop(T result, Context context) {
                int oldSize = (Integer) context.get("oldSize");
                T oldTop = (T) context.get("oldTop");
                return new Condition("returns element on top", oldTop, result).
                        and("size decreased by one", Stack.this.size() == oldSize - 1);
            }

            @Override
            public boolean empty(MethodInvocation<Boolean> methodInvocation) {
                return list.size() == 0;
            }

            @Override
            public int size(MethodInvocation<Integer> methodInvocation) {
                return list.size();
            }

        });
    }

    final public void push(T element) {
        new DesignByContract<Void>().build(StackBehaviour.class, Stack.class, "push", Object.class).execute(this, chain(), element);
    }

    final public T top() {
        return new DesignByContract<T>().build(StackBehaviour.class, Stack.class, "top").execute(this, chain());
    }

    final public T pop() {
        return new DesignByContract<T>().build(StackBehaviour.class, Stack.class, "pop").execute(this, chain());
    }

    final public boolean empty() {
        return new DesignByContract<Boolean>().build(StackBehaviour.class, Stack.class, "empty").execute(this, chain());
    }

    final public int size() {
        return new DesignByContract<Integer>().build(StackBehaviour.class, Stack.class, "size").execute(this, chain());
    }

    /*
    public static class PushContract <T> extends Contract<Stack<T>, Void> {

        private Stack<T> stack;
        private T element;
        private int oldSize;

        public PushContract(T element) {
            this.element = element;
        }

        @Override
        public void setTarget(Stack<T> target) {
            this.stack = target;
        }

        @Override
        public Condition pre() {
            this.oldSize = stack.size();

            return new Condition("element not null", element != null);
        }

        @Override
        public Condition post() {
            return new Condition("not empty", !stack.empty()).
               and("element on top", stack.top() == element).
               and("size increased by one", stack.size() == oldSize + 1);
        }

    }

    public static class TopContract <T> extends Contract<Stack<T>, T> {

        private Stack<T> stack;
        private int oldSize;

        @Override
        public void setTarget(Stack<T> target) {
            this.stack = target;
        }

        @Override
        public Condition pre() {
            this.oldSize = stack.size();

            return new Condition("not empty", !stack.empty());
        }

        @Override
        public Condition post() {
            return new Condition("size is unchanged", stack.size() == oldSize);
        }

    }

    public static class PopContract <T> extends Contract<Stack<T>, T> {

        private Stack<T> stack;
        private int oldSize;
        private Object oldTop;
        private T returnValue;

        @Override
        public void setReturnValue(T returnValue) {
            this.returnValue = returnValue;
        }

        @Override
        public void setTarget(Stack<T> target) {
            this.stack = target;
        }

        @Override
        public Condition pre() {
            this.oldSize = stack.size();
            if (!stack.empty()) {
                oldTop = stack.top();
            }

            return new Condition("not empty", !stack.empty());
        }

        @Override
        public Condition post() {
            return new Condition("returns element on top", oldTop, returnValue).
                and("size decreased by one", stack.size() == oldSize - 1);

        }

    }
    */

}
